package com.example.zhihu.config;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.example.zhihu.entity.Token;
import com.example.zhihu.entity.User;
import com.example.zhihu.service.ITokenService;
import com.example.zhihu.service.SysService;
import com.example.zhihu.util.JwtUtil;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Set;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Realm
 *
 * @author : Charles
 * @date : 2020/1/12
 */
@Slf4j
@Component("MyRealm")
public class MyRealm extends AuthorizingRealm {

  private SysService sysService;

  @Resource
  private ITokenService iTokenService;

  @Autowired
  public void setSysService(SysService sysService) {
    this.sysService = sysService;
  }

  /**
   * 必须重写此方法，不然Shiro会报错
   */
  @Override
  public boolean supports(AuthenticationToken token) {
    return token instanceof JwtToken;
  }

  /**
   * 用来进行身份认证，也就是说验证用户输入的账号和密码是否正确， 获取身份验证信息，错误抛出异常
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth)
      throws AuthenticationException {
    String token = (String) auth.getCredentials();
    if (null == token) {
      throw new AuthenticationException("token为空!");
    }
    // 解密获得username，用于和数据库进行对比
    String account = JwtUtil.parseTokenAud(token);
    User user = sysService.selectByAccount(account);
    if (null == user) {
      throw new AuthenticationException("用户不存在!");
    }
    // 校验token是否过期
    if (!tokenRefresh(token, user)) {
      throw new AuthenticationException("Token已过期!");
    }
    return new SimpleAuthenticationInfo(user, token, "MyRealm");
  }

  /**
   * 获取用户权限信息，包括角色以及权限。 只有当触发检测用户权限时才会调用此方法，例如checkRole,checkPermissionJwtToken
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    log.info("————权限认证 [ roles、permissions]————");
    User user = null;
    if (principals != null) {
      user = (User) principals.getPrimaryPrincipal();
    }
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    if (user != null) {
      // 用户拥有的角色，比如“admin/user”
      String role = sysService.getRoleByRoleid(user.getRoleId());
      simpleAuthorizationInfo.addRole(role);
      log.info("角色为：" + role);
      // 用户拥有的权限集合，比如“role:add,user:add”
      Set<String> permissions = sysService.getPermissionsByRoleId(user.getRoleId());
      simpleAuthorizationInfo.addStringPermissions(permissions);
      log.info("权限有：" + permissions.toString());
    }
    return simpleAuthorizationInfo;
  }

  /**
   * JWT Token续签： 业务逻辑：登录成功后，用户在未过期时间内继续操作，续签token。 登录成功后，空闲超过过期时间，返回token已失效，重新登录。
   * 实现逻辑：
   * 1.登录成功后将token存储到MySQL里面，并设置过期时间为token过期时间
   * 2.当用户请求时token值还未过期，则重新设置redis里token的过期时间。
   * 3.当用户请求时token值已过期，则用户空闲超时，返回token已失效，重新登录。
   */
  public boolean tokenRefresh(String token, User user) {
    Token dbTokenObj = iTokenService.getByUserId(user.getId());
    if(dbTokenObj == null) {
      throw new AuthenticationException("用户未登录");
    }
    LocalDateTime expiredTime = dbTokenObj.getExpiredTime();
    if(expiredTime.isBefore(LocalDateTime.now())) {
      throw new AuthenticationException("token过期，登录已失效");
    }

    // 重新设置超时时间
    dbTokenObj.setExpiredTime(LocalDateTimeUtil.offset(LocalDateTime.now(), 1, ChronoUnit.HOURS));
    iTokenService.updateById(dbTokenObj);

    String cacheToken = dbTokenObj.getToken();
    return true;
  }
}
